package org.btik.bean.impl;

import org.btik.bean.XmlMeta;
import org.btik.bean.impl.temporary.*;
import org.btik.context.exception.LightContextException;
import org.btik.light.tool.xml.DomUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;


public class BeanParser implements XmlMeta {

    private final CacheStruct<Object> completeBeans = new CacheStruct<>();

    private final CacheStruct<UnhandledReferencedBean> rawBeans = new CacheStruct<>();
    private final CacheStruct<UnConstructedBean> midProducts = new CacheStruct<>();

    private final LinkedHashSet<String> beanIdRefSet = new LinkedHashSet<>();

    private String path;

    BeanParser(String path) {
        this.path = path;
        BeanCreator.classLoader = Thread.currentThread().getContextClassLoader();
        load();
    }

    private void load() {
        try {
            InputStream confFile = getConfFile();
            Document dom = DomUtil.parse(confFile);
            parseBeans(dom);
        } catch (ParserConfigurationException | SAXException | IOException e) {
            throw new LightContextException("load context conf failed", e);
        }
    }

    private void parseBeans(Document dom) {
        Element rootNode = dom.getDocumentElement();
        String tagName = rootNode.getTagName();
        if (!BEANS.equals(tagName)) {
            throw new LightContextException("Illegal xml root tag [" + tagName + "]");
        }

        DomUtil.eachByTagName(rootNode, BEAN, (bean) -> {
            String beanClass = bean.getAttribute(BEAN_CLASS);
            String beanId = bean.getAttribute(BEAN_ID);
            if (beanClass.isEmpty()) {
                throw new LightContextException("without Bean class:" + bean);
            }
            try {
                parseBean(bean, beanClass, beanId);
            } catch (Exception e) {
                throw new LightContextException("parse bean failed " + beanId + '@' + beanClass, e);
            }
        });

        midProducts.resolve(this::resolveMidProduct);
        // 无id的未解决引用的bean，不会被依赖
        rawBeans.resolve((beanId, unhandledReferencedBean) -> {
                    List<BeanProp> props = unhandledReferencedBean.getProps();
                    Object rawBean = unhandledReferencedBean.getRawBean();
                    for (BeanProp prop : props) {
                        Object refBean = getRefBean(prop.getRef());
                        BeanCreator.setValue(rawBean, prop.getName(), refBean);
                    }
                    completeBeans.save(beanId, rawBean);
                }
        );

        BeanCache.store(completeBeans);

    }

    private void resolveMidProduct(String beanId, UnConstructedBean unConstructedBean) {

        Object rawBean;
        // 处理构造函数依赖
        List<ConstructorArg> args = unConstructedBean.getArgs();
        Object[] constructorArg = unConstructedBean.getConstructorArg();
        for (ConstructorArg arg : args) {
            Object parsedValue = arg.getParsedValue();
            if (parsedValue != null) {
                Object refBean = getRefBean(arg.getRef());
                constructorArg[arg.getIndex()] = refBean;
            }
        }
        // 创建对象
        try {
            rawBean = unConstructedBean.getConstructor().newInstance(constructorArg);
        } catch (Exception e) {
            throw new LightContextException(e);
        }
        // 属性赋值
        List<BeanProp> props = unConstructedBean.getProps();
        for (BeanProp prop : props) {
            if (prop.hasValue()) {
                BeanCreator.setCommonTypeValue(rawBean, prop.getName(), prop.getValue());
            } else {
                Object refBean = getRefBean(prop.getRef());
                BeanCreator.setValue(rawBean, prop.getName(), refBean);
            }
        }
        Object completeBean = rawBean;
        completeBeans.save(beanId, completeBean);
    }

    /**
     * 尝试返回，完全体bean,半成品bean，发现是中间产物时，会尝试创建完全体bean返回
     * ，但构造方法发生循环依赖会无法创建对象，将抛出异常，防止无限递归
     */
    private Object getRefBean(String beanId) {

        Object referencedBean = completeBeans.getIdBeanMap().get(beanId);
        if (referencedBean != null) {
            return referencedBean;
        }
        UnhandledReferencedBean referencedRawBean = rawBeans.getIdBeanMap().get(beanId);
        if (referencedRawBean != null) {
            referencedBean = referencedRawBean.getRawBean();
            return referencedBean;
        }
        UnConstructedBean unConstructedBean = midProducts.getIdBeanMap().get(beanId);
        if (unConstructedBean == null) {
            throw new LightContextException("cannot reference such a bean with this beanId :" + beanId);
        }

        try {
            if (beanIdRefSet.contains(beanId)) {
                throw new LightContextException("Constructor injection does not allow circular dependencies:\n" + beanIdRefSet);
            }
            beanIdRefSet.add(beanId);
            resolveMidProduct(beanId, unConstructedBean);
            return completeBeans.getIdBeanMap().get(beanId);
        } finally {
            beanIdRefSet.remove(beanId);
        }
    }

    private void parseBean(Element bean, String beanClass, String beanId) {

        // 解析构造方法，并创建对象
        List<ConstructorArg> constructorArgs = parseArgs(bean);
        Object instance = BeanCreator.newInstance(beanClass, beanId, constructorArgs);
        if (instance instanceof UnConstructedBean) {
            UnConstructedBean unConstructedBean = (UnConstructedBean) instance;
            parseProp(unConstructedBean, bean);
            midProducts.save(beanId, unConstructedBean);
            return;
        }

        // 属性解析
        UnhandledReferencedBean unhandledReferencedBean = parseProp(instance, bean);
        if (null != unhandledReferencedBean) {
            unhandledReferencedBean.setBeanId(beanId);
            rawBeans.save(beanId, unhandledReferencedBean);
            return;
        }
        // 完全解析的对象
        completeBeans.save(beanId, instance);

    }

    private void parseProp(UnConstructedBean unConstructedBean, Element bean) {
        List<BeanProp> props = parseProp(bean);
        unConstructedBean.setProps(props);
    }

    private UnhandledReferencedBean parseProp(Object instance, Element bean) {
        UnhandledReferencedBean unhandledReferencedBean = null;
        List<BeanProp> props = parseProp(bean);
        for (BeanProp prop : props) {
            if (prop.hasValue()) {
                BeanCreator.setCommonTypeValue(instance, prop.getName(), prop.getValue());
            } else if (unhandledReferencedBean == null) {
                unhandledReferencedBean = new UnhandledReferencedBean();
                unhandledReferencedBean.setRawBean(instance);
                unhandledReferencedBean.addProp(prop);
            }
        }

        // 存在引用，则存入rawBean
        if (unhandledReferencedBean != null) {
            unhandledReferencedBean.setRawBean(instance);
        }
        return unhandledReferencedBean;
    }

    private List<BeanProp> parseProp(Element bean) {
        ArrayList<BeanProp> props = new ArrayList<>();
        DomUtil.eachByTagName(bean, PROP, (prop) -> {
            String name = prop.getAttribute(PROP_NAME);
            String value = prop.getAttribute(PROP_VAL);
            String ref = prop.getAttribute(PROP_REF);
            if (name.isEmpty()) {
                throw new LightContextException("property name must be specified ");
            }
            // value可以指定为空，但不能同时没有指定值和引用
            boolean hasValue = prop.hasAttribute(PROP_VAL);
            if (!hasValue && ref.isEmpty()) {
                throw new LightContextException("value or ref must be specified :" + name);
            }
            props.add(new BeanProp(name, value, hasValue, ref));
        });
        return props;
    }

    private List<ConstructorArg> parseArgs(Element bean) {
        List<ConstructorArg> args = new ArrayList<>();
        DomUtil.eachByTagName(bean, CONSTRUCTOR_ARG, (arg) -> {
            String name = arg.getAttribute(PROP_NAME);
            boolean hasValue = arg.hasAttribute(PROP_VAL);
            String value = arg.getAttribute(PROP_VAL);
            String ref = arg.getAttribute(PROP_REF);
            String type = arg.getAttribute(TYPE);
            if (name.isEmpty() && type.isEmpty()) {
                throw new LightContextException("Name or type must be filled in");
            }
            // value可以指定为空，但不能同时没有指定值和引用
            if (!hasValue && ref.isEmpty()) {
                throw new LightContextException("value or ref must be specified");
            }
            args.add(new ConstructorArg(name, value, hasValue, ref, type));
        });
        return args;
    }


    private InputStream getConfFile() throws IOException {
        if (null == path) {
            path = LIGHT_CONTEXT_CONF;
        }
        File file = new File(path);
        if (file.exists()) {
            return new FileInputStream(file);
        }
        URL resource = BeanParser.class.getResource('/' + path);
        if (resource == null) {
            throw new LightContextException("context file [" + path + "] not found");
        }
        return resource.openStream();
    }


}
